<?php
// This file is part of POAS Assignment - https://code.google.com/p/oasychev-moodle-plugins
//
// Meta AGM grading method is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Meta AGM is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Meta AGM.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Submission management for POAS Assignment.
 *
 * @package    mod_poasassignment
 * @author     Nikita Kalinin <nixorv@gmail.com>
 * @copyright  2015 Oleg Sychev (Volgograd State Technical University) <oasychev@gmail.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace mod_poasassignment\submissions;

defined('MOODLE_INTERNAL') || die();

require_once($CFG->libdir . '/formslib.php');
require_once(dirname(dirname(__DIR__)) . '/model.php');
require_once(dirname(dirname(dirname(__DIR__))) . '/assign/locallib.php');

/**
 * Submission manager class to utilize Assign Submission plugins.
 *
 * @package    mod_poasassignment
 * @author     Nikita Kalinin <nixorv@gmail.com>
 * @copyright  2015 Oleg Sychev (Volgograd State Technical University) <oasychev@gmail.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class submission_manager {

    /**
     * Prepares the POAS Assignment to work with Assign Submissions.
     * Must be called once when POAS Assignment instance is created.
     * Note: POAS Assignment instance record must exist.
     */
    public static function init_module_instance(\stdClass $formdata) {
        global $DB;

        $fakeassignid = self::create_fake_assign();
        $fakeassigncmid = self::create_fake_cm($fakeassignid);

        $poasmodel = \poasassignment_model::get_instance();
        $poasmodel->poasassignment->fakeassign = $fakeassigncmid;
        $DB->update_record('poasassignment', $poasmodel->poasassignment);

        $ctx = $poasmodel->get_context();
        $assign = new \assign($ctx, null, null);
        $assign->set_instance($DB->get_record('assign', array('id' => $fakeassignid)));

        $submissionplugins = $assign->get_submission_plugins();
        foreach ($submissionplugins as $plugin) {
            if (!self::update_assign_plugin_instance($plugin, $formdata)) {
                print_error($plugin->get_error());
                return false;
            }
        }
    }

    /**
     * Cleans things up. Must be called once when POAS Assignment instance is deleted.
     */
    public static function finalize_module_instance() {
        global $DB;

        $poasmodel = \poasassignment_model::get_instance();
        $fakeassigncmid = $poasmodel->poasassignment->fakeassign;
        $fakeassignid = $DB->get_record('course_modules', array('id' => $fakeassigncmid), 'instance', MUST_EXIST)->instance;

        $ctx = self::get_assign_context($fakeassigncmid);
        $assign = new \assign($ctx, null, null);
        $assign->set_instance($DB->get_record('assign', array('id' => $fakeassignid)));

        $assign->delete_instance();
    }

    /**
     * Add settings to edit plugin form.
     *
     * @param \context_module|null $ctx
     * @param int $fakeassigncmid
     * @param \MoodleQuickForm $mform The form to add the configuration settings to.
     *                               This form is modified directly (not returned).
     * @throws \coding_exception
     */
    public static function add_subplugins_settings($ctx, $fakeassigncmid, \MoodleQuickForm $mform) {
        global $DB;

        $assign = new \assign($ctx, null, null);
        if ($ctx) {
            $fakeassigncm = $DB->get_record('course_modules', array('id' => $fakeassigncmid), '*', MUST_EXIST);
            $fakeassignid = $fakeassigncm->instance;
            $assign->set_instance($DB->get_record('assign', array('id' => $fakeassignid)));
            self::set_assign_course_module($assign, $fakeassigncm);
        }

        $submissionplugins = $assign->get_submission_plugins();

        $mform->addElement('header', 'submissiontypes', get_string('submissiontypes', 'assign'));

        $submissionpluginsenabled = array();
        $group = $mform->addGroup(array(), 'submissionplugins', get_string('submissiontypes', 'assign'), array(' '), false);
        foreach ($submissionplugins as $plugin) {
            self::add_assign_plugin_settings($assign, $plugin, $mform, $submissionpluginsenabled);
        }
        $group->setElements($submissionpluginsenabled);
        $mform->setExpanded('submissiontypes');
    }

    /**
     * Renders the submissions.
     */
    public static function show_submission($assigneeid, $attemptid) {
        global $DB;

        $poasmodel = \poasassignment_model::get_instance();
        if (!$poasmodel->get_last_attempt($assigneeid)) {
            return;
        }
        $user = $poasmodel->get_user_by_assigneeid($assigneeid);
        $fakeassigncmid = $poasmodel->poasassignment->fakeassign;
        $fakeassignid = $DB->get_record('course_modules', array('id' => $fakeassigncmid), 'instance', MUST_EXIST)->instance;

        $ctx = self::get_assign_context($fakeassigncmid);
        $assign = new \assign($ctx, null, null);
        $assign->set_instance($DB->get_record('assign', array('id' => $fakeassignid)));
        $assign->set_course($poasmodel->get_course());

        $displaymode = \assign_submission_plugin_submission::FULL;
        $submission = $assign->get_user_submission($user->userid, false);
        $o = '';
        foreach ($assign->get_submission_plugins() as $plugin) {
            if ($plugin->get_name() != get_string('pluginname', 'assignsubmission_comments')) {
                $pluginsubmission = new \assign_submission_plugin_submission($plugin,
                    $submission,
                    $displaymode,
                    $poasmodel->get_cm()->id,
                    '',
                    null);
                $o .= $assign->get_renderer()->render($pluginsubmission);
            }
        }

        return $o;
    }

    /**
     * Adds submission plugin form elements to a grading form.
     * @param \MoodleQuickForm $mform
     */
    public static function add_submission_form_elements(\MoodleQuickForm $mform) {
        global $DB, $USER;

        $poasmodel = \poasassignment_model::get_instance();
        $fakeassigncmid = $poasmodel->poasassignment->fakeassign;
        $fakeassigncm = $DB->get_record('course_modules', array('id' => $fakeassigncmid), '*', MUST_EXIST);
        $fakeassignid = $fakeassigncm->instance;

        $ctx = self::get_assign_context($fakeassigncmid);
        $assign = new \assign($ctx, null, null);
        self::set_assign_course_module($assign, $fakeassigncm);
        $assign->set_instance($DB->get_record('assign', array('id' => $fakeassignid)));
        $assign->set_course($poasmodel->get_course());

        $data = new \stdClass();
        $data->userid = $USER->id;
        $assign->add_submission_form_elements($mform, $data);
    }

    /**
     * Save submission.
     * @param \stdClass $data
     * @return bool
     */
    public static function save_submission(\stdClass $data) {
        global $DB, $USER;

        $poasmodel = \poasassignment_model::get_instance();
        $fakeassigncmid = $poasmodel->poasassignment->fakeassign;
        $fakeassigncm = $DB->get_record('course_modules', array('id' => $fakeassigncmid), '*', MUST_EXIST);
        $fakeassignid = $fakeassigncm->instance;

        $ctx = self::get_assign_context($fakeassigncmid);
        $assign = new \assign($ctx, null, null);
        self::set_assign_course_module($assign, $fakeassigncm);
        $assign->set_instance($DB->get_record('assign', array('id' => $fakeassignid)));
        $assign->set_course($poasmodel->get_course());

        $instance = $assign->get_instance();
        if ($instance->teamsubmission) {
            $submission = $assign->get_group_submission($USER->id, 0, true);
        } else {
            $submission = $assign->get_user_submission($USER->id, true);
        }
        if ($instance->submissiondrafts) {
            $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
        } else {
            $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
        }

        $notices = array();
        $pluginerror = false;
        foreach ($assign->get_submission_plugins() as $plugin) {
            if ($plugin->is_enabled() && $plugin->is_visible()) {
                if (!$plugin->save($submission, $data)) {
                    $notices[] = $plugin->get_error();
                    $pluginerror = true;
                }
            }
        }

        $allempty = $assign->submission_empty($submission);
        if ($pluginerror || $allempty) {
            if ($allempty) {
                $notices[] = get_string('submissionempty', 'mod_assign');
            }
            return false;
        }

        return true;
    }

    /**
     * Creates a context object for given assign cmid.
     * @param $fakeassigncmid
     * @return mixed
     */
    private static function get_assign_context($fakeassigncmid) {
        global $DB;

        $record = $DB->get_record('context', array('contextlevel' => 70, 'instanceid' => $fakeassigncmid));

        $poasmodel = \poasassignment_model::get_instance();
        $ctx = clone $poasmodel->get_context();

        // Never try this!
        $idprop = new \ReflectionProperty('\context_module', '_id');
        $idprop->setAccessible(true);
        $levelprop = new \ReflectionProperty('\context_module', '_contextlevel');
        $levelprop->setAccessible(true);
        $instanceprop = new \ReflectionProperty('\context_module', '_instanceid');
        $instanceprop->setAccessible(true);
        $pathprop = new \ReflectionProperty('\context_module', '_path');
        $pathprop->setAccessible(true);
        $depthprop = new \ReflectionProperty('\context_module', '_depth');
        $depthprop->setAccessible(true);

        if ($record) {
            $idprop->setValue($ctx, $record->id);
            $instanceprop->setValue($ctx, $record->instanceid);
        } else {
            $record = new \stdClass();
            $record->contextlevel = $levelprop->getValue($ctx);
            $record->instanceid = $fakeassigncmid;
            $record->path = $pathprop->getValue($ctx);
            $record->depth = $depthprop->getValue($ctx);
            $id = $DB->insert_record('context', $record);
            $idprop->setValue($ctx, $id);
            $instanceprop->setValue($ctx, $fakeassigncmid);
        }

        return $ctx;
    }



    /**
     * Creates a fake assign to access submission plugins.
     * @return int Assign id.
     */
    private static function create_fake_assign() {
        global $DB;

        $poasmodel = \poasassignment_model::get_instance();

        $fakeassign = new \stdClass();
        $fakeassign->name = 'Fake for PA ' . $poasmodel->poasassignment->id;
        $fakeassign->intro = 'Fake Assign instance for POAS Assignment id ' . $poasmodel->poasassignment->id;
        return $DB->insert_record('assign', $fakeassign, true);
    }

    /**
     * Creates a fake course module for fake assign.
     * @param $instanceid
     * @return bool|int
     */
    private static function create_fake_cm($instanceid) {
        global $DB;

        $fakecm = new \stdClass();
        $fakecm->instance = (int)$instanceid;
        $fakecm->course = 2;
        $fakecm->module = $DB->get_record('modules', array('name' => 'assign'), 'id', MUST_EXIST)->id;
        return $DB->insert_record('course_modules', $fakecm);
    }

    /**
     * Very magic setter for \assign protected field coursemodule.
     * @param \assign $assign
     * @param $coursemodule
     */
    private static function set_assign_course_module(\assign &$assign, &$coursemodule) {

        // Never try this!
        $prop = new \ReflectionProperty('\assign', 'coursemodule');
        $prop->setAccessible(true);
        $prop->setValue($assign, $coursemodule);
    }

    /**
     * Add one plugins settings to edit plugin form.
     *
     * @param \assign $assign
     * @param \assign_plugin $plugin The plugin to add the settings from
     * @param \MoodleQuickForm $mform The form to add the configuration settings to.
     *                               This form is modified directly (not returned).
     * @param array $pluginsenabled A list of form elements to be added to a group.
     *                              The new element is added to this array by this function.
     * @throws \Exception
     * @throws \dml_exception
     */
    private static function add_assign_plugin_settings(\assign $assign, \assign_plugin $plugin, \MoodleQuickForm $mform, & $pluginsenabled) {
        global $CFG;
        if ($plugin->is_visible() && !$plugin->is_configurable() && $plugin->is_enabled()) {
            $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
            $pluginsenabled[] = $mform->createElement('hidden', $name, 1);
            $mform->setType($name, PARAM_BOOL);
            $plugin->get_settings($mform);
        } else if ($plugin->is_visible() && $plugin->is_configurable()) {
            $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
            $label = $plugin->get_name();
            $label .= ' ' . $assign->get_renderer()->help_icon('enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
            $pluginsenabled[] = $mform->createElement('checkbox', $name, '', $label);

            $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
            if ($plugin->get_config('enabled') !== false) {
                $default = $plugin->is_enabled();
            }
            $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);

            $plugin->get_settings($mform);

        }
    }

    /**
     * Update the settings for a single plugin.
     *
     * @param \assign_plugin $plugin The plugin to update
     * @param \stdClass $formdata The form data
     * @return bool false if an error occurs
     * @throws \moodle_exception
     */
    private static function update_assign_plugin_instance(\assign_plugin $plugin, \stdClass $formdata) {
        if ($plugin->is_visible()) {
            $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
            if (!empty($formdata->$enabledname)) {
                $plugin->enable();
                if (!$plugin->save_settings($formdata)) {
                    print_error($plugin->get_error());
                    return false;
                }
            } else {
                $plugin->disable();
            }
        }
        return true;
    }
}